﻿#region namespaces

using System;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Xml.Linq;
using ZOIL.DataBackend.BaseX.XQueries;
using ZOIL.DataModel;

#endregion

namespace ZOIL.DataBackend.BaseX
{
    public sealed partial class Database
    {
        #region ID

        private long AllocateId()
        {
            var query = XQueriesLoader.GetAllocateIdQuery(DbConfig.DatabaseName);
            var id = _client.QuerySingle<long>(query, DbConfig.DatabaseName);
            return id;
        }

        #endregion

        #region Store

        public void Store(IPersistable obj, IVisualProperties prop)
        {
            //null?
            if (obj.GetType().IsClass && obj == null)
            {
                throw new ArgumentException("Passed object is null", "obj");
            }
            //null?
            if (prop.GetType().IsClass && prop == null)
            {
                throw new ArgumentException("Passed VisualProperties is null", "prop");
            }

            //überprüfen: schon in Db?
            if (!obj.DatabaseId.IsDefault())
            {
                throw new ArgumentException(string.Format("Object {0} is already stored in the database (-> DatabaseId != default)", obj), "obj");
            }

            var t = obj.GetType();
            //generische Typen werden nicht unterstützt
            if (t.IsGenericType)
            {
                throw new ArgumentException(
                    string.Format(
                        "Object {0} is generic type which is currently not supported! (because you can't identify generic instances by type (List`1: int, string, ...?)",
                        obj), "obj");
            }

            var tp = prop.GetType();

            //Id aus Database holen
            var id = AllocateId();
            obj.DatabaseId = id;
            prop.BelongsToDatabaseId = id;

            //Pfad für obj ermitteln
            Debug.Assert(t.Namespace != null);
            var pathObj = t.Namespace.Replace('.', '/');

            //Pfad für prop ermitteln
            Debug.Assert(tp.Namespace != null);
            var pathProp = tp.Namespace.Replace('.', '/');

            //serializieren
            var xmlObj = Serializer.Serialize(obj);
            var xmlProp = Serializer.Serialize(prop);

            //speichern & event auslösen
            var query = XQueriesLoader.GetStoreObjectPairQuery(DbConfig.DatabaseName, DbConfig.EventName, EventType.Add,
                                                               id, pathObj, xmlObj, pathProp, xmlProp);
            //<{2} Id="{3}">{{$objXml, $propXml}}</{2}>
            var evt = new XElement(EventType.Add,
                                   new XAttribute("Id", id),
                                   DbConfig.AddAttributeToXElementWithPathNamespace(xmlObj, "Path", obj.GetType().Namespace + "." + obj.GetType().Name),
                                   DbConfig.AddAttributeToXElementWithPathNamespace(xmlProp, "Path",
                                                                                    prop.GetType().Namespace + "." + prop.GetType().Name)
                );

            //speichern und event in unterschiedlichen session damit event nicht bei sender ankommt
            var tStore = Task.Factory.StartNew(() => _client.QuerySingle(query));
            var tEvent = Task.Factory.StartNew(() => SendEvent(evt));

            tStore.Wait();
            tEvent.Wait();

            //in Buffer speichern
            StoreLocally(ref obj, ref prop);
        }

        private void StoreLocally(ref IPersistable obj, ref IVisualProperties prop)
        {
            //in Buffer speichern
            _localDbObjects.Store(ref obj, ref prop);
            ListenToChangesIfPossible(obj, prop);
        }

        public void Add(IPersistable p, IVisualProperties prop)
        {
            Store(p, prop);
        }

        #endregion
    }
}